﻿/*********************************************************
 * 开发人员：小兵
 * 创建时间：2012-10-22 14:17:55
 * 描述说明：
 * 
 * 更改历史：
 * 
 * *******************************************************/
namespace Mozlite.WebUI
{
    using System;
    using System.Web;
    using System.Data;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Mozlite.Components;
    using System.Text.RegularExpressions;
    using System.Web.Mvc;
    using System.Web.Routing;
    using System.IO;
    using WebRoute = Mozlite.Components.Route;
    using SysRoute = System.Web.Routing.Route;

    /// <summary>
    /// 路由管理类。
    /// </summary>
    public static class Routes
    {
        #region helpers
        private static string Replace(string url, string area = null)
        {
            url = url.ToLower();
            if(area != null)
                area += "/";
            url = url.Replace("~/", area);
            return url + GlobalSettings.Current.FileExtension;
        }

        /// <summary>
        /// 获取唯一路由名称。
        /// </summary>
        /// <param name="area">区域名称。</param>
        /// <param name="name">名称。</param>
        /// <returns>返回路由名称。</returns>
        private static string GetRouteName(string area, string name)
        {
            if(string.IsNullOrEmpty(area))
                return name.ToLower();
            return (area + "::" + name).ToLower();
        }
        #endregion

        #region caches
        private static readonly Regex keyRegex = new Regex("\\{([a-z]+?)\\}", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        private static readonly Dictionary<string, string> urlsCaches = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        private static string GetRouteUrl(string name, RouteValueDictionary values)
        {
            using(RouteTable.Routes.GetReadLock())
            {
                var route = RouteTable.Routes[name] as System.Web.Routing.Route;
                if(route == null)
                    return "#";
                //throw new Exception("The route named “" + name + "” cannot be found!");

                var url = Cores.GetUrl(keyRegex.Replace(route.Url, match =>
                {
                    string key = match.Groups[1].Value;
                    object value = null;
                    if(values != null && values.ContainsKey(key))
                        value = values[key];
                    if((value == null || string.IsNullOrEmpty(value.ToString())) && route.Defaults != null && route.Defaults.ContainsKey(key))
                        value = route.Defaults[key];
                    return (value ?? "").ToString();
                }));
                var domainRoute = route as DomainRoute;
                if(domainRoute != null && !string.IsNullOrEmpty(domainRoute.Domain))
                    url = Path.Combine("http://" + domainRoute.Domain, url);
                return url.ToLower();
            }
        }
        private static readonly object locker = new object();

        private static string GetCacheUrl(string key)
        {
            lock(locker)
            {
                string url;
                if(urlsCaches.TryGetValue(key, out url))
                    return url;
            }
            return null;
        }

        private static string AddCacheUrl(string key, string url)
        {
            if(string.IsNullOrEmpty(url))
                return null;
            lock(locker)
            {
                if(!urlsCaches.ContainsKey(key))
                    urlsCaches.Add(key, url);
            }
            return url;
        }

        private static string GetRouteDefaultUrl(string area, string key)
        {
            using(RouteTable.Routes.GetReadLock())
            {
                var route = RouteTable.Routes[key] as DomainRoute;
                if(route == null)
                    return Cores.GetUrl(area).ToLower();
                return "http://" + route.Domain;
            }
        }
        #endregion

        #region formatters
        /// <summary>
        /// 格式化URL地址。
        /// </summary>
        /// <param name="name">路由名称。</param>
        /// <param name="values">参数键值对。</param>
        /// <param name="area">区域名称。</param>
        /// <returns>返回URL地址。</returns>
        internal static string FormatUrl(string name, object values, string area = null)
        {
            return FormatUrl(name, new RouteValueDictionary(values), area);
        }

        /// <summary>
        /// 格式化URL地址。
        /// </summary>
        /// <param name="name">路由名称。</param>
        /// <param name="values">参数键值对。</param>
        /// <param name="area">区域名称。</param>
        /// <returns>返回URL地址。</returns>
        internal static string FormatUrl(string name, RouteValueDictionary values = null, string area = null)
        {
            name = GetRouteName(area, name);
            string url = null;
            if(values == null)
            {
                url = GetCacheUrl(name);
                if(url != null)
                    return url;
                url = GetRouteUrl(name, null);
                return AddCacheUrl(name, url);
            }
            return GetRouteUrl(name, values);
        }

        /// <summary>
        /// 获取首页地址。
        /// </summary>
        /// <param name="area">区域名称。</param>
        /// <returns>返回首页默认地址。</returns>
        internal static string GetDefaultUrl(string area = null)
        {
            if(area == null)
                return Cores.GetUrl();
            string key = GetRouteName(area, "Index");
            string url = GetCacheUrl(key);
            if(url == null)
                url = AddCacheUrl(key, GetRouteDefaultUrl(area, key));
            return url;
        }
        #endregion

        #region Register
        /// <summary>
        /// 注册DLL中的路由。
        /// </summary>
        /// <param name="routes">路由集合。</param>
        public static void RegisterDll(this RouteCollection routes)
        {
            foreach(var route in Cores.FindType<BaseRouteRegistration>())
            {
                route.Register(routes);
            }
        }

        /// <summary>
        /// 注册DLL中的路由。
        /// </summary>
        /// <param name="context">对在 ASP.NET MVC 应用程序内注册某个区域时所需的信息进行封装。</param>
        public static void RegisterDll(this AreaRegistrationContext context)
        {
            foreach(var route in Cores.FindType<BaseRouteRegistration>())
            {
                route.Register(context);
            }
        }

        /// <summary>
        /// 注册数据库里的区域路由。
        /// </summary>
        /// <param name="context">对在 ASP.NET MVC 应用程序内注册某个区域时所需的信息进行封装。</param>
        public static void RegisterDB(this AreaRegistrationContext context)
        {
            Dictionary<string, WebRoute> routes;
            if(Components.Routes.LoadRoutes().TryGetValue(context.AreaName, out routes))
            {
                foreach(var route in routes)
                {
                    context.Routes.Add(GetRouteName(context.AreaName, route.Key),
                        new SysRoute(Replace(route.Value.Url, context.AreaName),
                            WebRoute.FromString(route.Value.Defaults),
                            WebRoute.FromString(route.Value.Constraints),
                            new RouteValueDictionary { 
                    { "Namespaces", route.Value.Namespaces == null ? context.Namespaces.ToArray<string>(): route.Value.Namespaces.Split(new string[]{"\r\n"}, StringSplitOptions.RemoveEmptyEntries) },
                    { "Area", context.AreaName}
                    }, route.Value.RouteHandler));
                }
            }
        }

        /// <summary>
        /// 注册数据库里的路由。
        /// </summary>
        /// <param name="routes">路由集合。</param>
        public static void RegisterDB(this RouteCollection routes)
        {
            Dictionary<string, WebRoute> urls;
            if(Components.Routes.LoadRoutes().TryGetValue(Components.Routes.NullArea, out urls))
            {
                foreach(var route in urls)
                {
                    if(route.Value.Namespaces.HasValue())
                        routes.Add(route.Key,
                            new SysRoute(Replace(route.Value.Url),
                                WebRoute.FromString(route.Value.Defaults),
                                WebRoute.FromString(route.Value.Constraints),
                                new RouteValueDictionary { { "Namespaces", route.Value.Namespaces.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries) } }, route.Value.RouteHandler));
                    else
                        routes.Add(route.Key, new SysRoute(Replace(route.Value.Url), WebRoute.FromString(route.Value.Defaults), WebRoute.FromString(route.Value.Constraints), route.Value.RouteHandler));
                }
            }
        }

        /// <summary>
        /// 注册路由。
        /// </summary>
        /// <param name="routes">路由集合。</param>
        /// <param name="name">名称，唯一</param>
        /// <param name="url">Url地址。</param>
        /// <param name="defaults">默认值。</param>
        /// <param name="constraints">正则表达式。</param>
        /// <param name="namespaces">命名空间。</param>
        public static void Register(this RouteCollection routes, string name, string url = null, object defaults = null, object constraints = null, string[] namespaces = null, string domain = null)
        {
            if(namespaces == null)
                namespaces = new[] { "Mozlite.WebUI.Mvc4.Controllers.*" };
            if(defaults == null)
                defaults = new { controller = "home", action = "Index", id = UrlParameter.Optional };
            if(domain != null)
                routes.Add(name, new DomainRoute(domain, Replace(url), defaults, new RouteValueDictionary(constraints), new RouteValueDictionary { { "Namespaces", namespaces }}));
            else
                routes.MapRoute(name, Replace(url), defaults, constraints, namespaces);
        }

        /// <summary>
        /// 注册区域路由。
        /// </summary>
        /// <param name="context">区域注册上下文。</param>
        /// <param name="name">唯一键。</param>
        /// <param name="url">Url地址。</param>
        /// <param name="defaults">默认值。</param>
        /// <param name="constraints">正则表达式。</param>
        /// <param name="namespaces">命名空间集合。</param>
        /// <param name="ignoreSubDomain">忽略二级域名。</param>
        public static void Register(this AreaRegistrationContext context, string name, string url = null, object defaults = null, object constraints = null, string[] namespaces = null, string domain = null)
        {
            if(namespaces == null)
                namespaces = context.Namespaces.ToArray<string>();
            if(defaults == null)
                defaults = new { controller = "home", action = "Index", id = UrlParameter.Optional };
            if(domain != null)
                context.Routes.Add(GetRouteName(context.AreaName, name), new DomainRoute(domain, Replace(url), defaults, new RouteValueDictionary(constraints), new RouteValueDictionary { 
                    { "Namespaces", namespaces },
                    { "Area", context.AreaName}
                    }));
            else
                context.MapRoute(GetRouteName(context.AreaName, name), Replace(url, context.AreaName), defaults, constraints, namespaces);
        }
        #endregion
    }
}
